--------The Fourth R - Reasoning-------
A 4am crack                  2017-02-01
---------------------------------------

Name: The Fourth R - Reasoning
Genre: educational
Year: 1984
Publisher: MCE
Platform: Apple ][+ or later
Media: single-sided 5.25-inch floppy
OS: DOS 3.3
Previous cracks: none (of this version)
  Asimov has a later version; see
  http://www.hackzapple.com/
  phpBB2/viewtopic.php?t=929
  for qkumba's write-up of that one

                   ~

               Chapter 0
 In Which Various Automated Tools Fail
          In Interesting Ways


COPYA
  immediate disk read error

Locksmith Fast Disk Backup
  unable to read anything other than
  track $00

EDD 4 bit copy (no sync, no count)
  no read errors, but copy reboots
  endlessly

Copy ][+ nibble editor
  Every track (other than track $00)
  uses a different address prologue.
  Track $01 is "D5 AA 97",
  Track $02 is "D5 AA 9A",
  Track $03 is "D5 AA 9B",
  Track $11 is "D5 AA B5", &c.

Disk Fixer
  Track $00 looks like a DOS 3.3-shaped
  bootloader and RWTS
  ["O" -> "Input/Output Control"]
    set Address Prologue to "D5 AA B5"
  Track $11 readable! I can even press
  "D" to get a directory and select a
  file (which fails to read because it
  is stored on a different track with a
  different prologue, but OK)

                 --v--

------------ DIRECTORY MODE -----------
$13:$0F  HELLO
$14:$0F  MCE INTRO
$15:$0F  LE1.SA
$16:$0F  LE2.SA
$17:$0F  LE3.SA
$18:$0F  LE4.SA
$19:$0F  SW16/MAIN/HRCG
$1A:$0F  LE1.GT
$1B:$0F  APSOFT/MAIN INTERFACE
$1C:$0F  LE2.GT
$1D:$0F  LE3.GT
$1E:$0F  LE4.GT
$1F:$0F  BADWS
$20:$0F  SOUND TAB/CHRSETA

-----------------DOS3.3----------------

                 --^--

Why didn't COPYA work?
  modified address prologue on T01+

Why didn't Locksmith FDB work?
  ditto

Why didn't my EDD copy work?
  I don't know. Probably a runtime
  protection check in early boot. Disks
  do not simply reboot unless someone
  tells them to.

Next steps:

  1. Trace the boot to capture the RWTS
  2. Use Advanced Demuffin to convert
     the disk to a standard format
  3. Find and disable the protection
     check
  4. Declare victory (*)

(*) Go to the gym

                   ~

               Chapter 1
      In Which We Brag About Our
           Humble Beginnings


I have two floppy drives, one in slot 6
and the other in slot 5. My "work disk"
(in slot 5) runs Diversi-DOS 64K, which
is compatible with Apple DOS 3.3 but
relocates most of DOS to the language
card on boot. This frees up most of
main memory (only using a single page
at $BF00..$BFFF), which is useful for
loading large files or examining code
that lives in areas typically reserved
for DOS.

[S6,D1=original disk]
[S5,D1=my work disk]

The floppy drive firmware code at $C600
is responsible for aligning the drive
head and reading sector 0 of track 0
into main memory at $0800. Because the
drive can be connected to any slot, the
firmware code can't assume it's loaded
at $C600. If the floppy drive card were
removed from slot 6 and reinstalled in
slot 5, the firmware code would load at
$C500 instead.

To accommodate this, the firmware does
some fancy stack manipulation to detect
where it is in memory (which is a neat
trick, since the 6502 program counter
is not generally accessible). However,
due to space constraints, the detection
code only cares about the lower 4 bits
of the high byte of its own address.

$C600 (or $C500, or anywhere in $Cx00)
is read-only memory. I can't change it,
which means I can't stop it from
transferring control to the boot sector
of the disk once it's in memory. BUT!
The disk firmware code works unmodified
at any address. Any address that ends
with $x600 will boot slot 6, including
$B600, $A600, $9600, &c.

; copy drive firmware to $9600
*9600<C600.C6FFM

; and execute it
*9600G
...reboots slot 6, loads game...

Now then:

]PR#5
...
]CALL -151

*9600<C600.C6FFM

*96F8L

96F8-   4C 01 08    JMP   $0801

That's where the disk controller ROM
code ends and the on-disk code begins.
But $9600 is part of read/write memory.
I can change it at will. So I can
interrupt the boot process after the
drive firmware loads the boot sector
from the disk but before it transfers
control to the disk's bootloader.

; instead of jumping to on-disk code,
; copy boot sector to higher memory so
; it survives a reboot
96F8-   A0 00       LDY   #$00
96FA-   B9 00 08    LDA   $0800,Y
96FD-   99 00 28    STA   $2800,Y
9700-   C8          INY
9701-   D0 F7       BNE   $96FA

; turn off slot 6 drive motor
9703-   AD E8 C0    LDA   $C0E8

; reboot to my work disk in slot 5
9706-   4C 00 C5    JMP   $C500

*BSAVE TRACE0,A$9600,L$109
*9600G
...reboots slot 6...
...reboots slot 5...

]BSAVE BOOT0,A$2800,L$100

Now let's see how this disk boots.

                   ~

               Chapter 2
            We Patch Along


]CALL -151

*800<2800.28FFM

*801L
.
. nothing unusual, until...
.
0848-   A6 2B       LDX   $2B
084A-   4C BC B6    JMP   $B6BC

In a standard DOS 3.3 bootloader (which
this is a variant of), sector 0 is
loaded twice -- once at $0800, then
again at $B600. So I have the code at
$B6BC, because it's currently at $08BC.

*B600<800.8FFM

*B6BCL

; copy something somewhere
B6BC-   A0 33       LDY   #$33
B6BE-   B9 CA B6    LDA   $B6CA,Y
B6C1-   99 71 A9    STA   $A971,Y
B6C4-   88          DEY
B6C5-   10 F7       BPL   $B6BE

; and continue to a place that usually
; has no code in it
B6C7-   4C 00 BB    JMP   $BB00

*A971<B6CA.B6FFM

*A971L

; not sure what's in the accumulator on
; entry, since I don't know where this
; is called from yet...
A971-   48          PHA
A972-   A8          TAY
A973-   B9 81 A9    LDA   $A981,Y

; ...but whatever it was, we're using
; it as an index and fiddling with RWTS
; parameters (address prologues!)
A976-   8D 84 BC    STA   $BC84
A979-   8D 6A B9    STA   $B96A
A97C-   A0 01       LDY   #$01
A97E-   4C 5D BE    JMP   $BE5D

Given my initial investigation with a
nibble editor, I'm guessing this little
routine is being called with the track
in the accumulator.

Here's the array of values:

*A981.

A981- .. 96 97 9A 9B 9D 9E 9F
A988- A6 A7 AB AC DA AE AF B2
A990- B3 B4 B5 B6 B7 B9 BA BB
A998- BC BD BE BF CB CD CE CF
A9A0- D3 D6 D7 D9

That lines up with the tracks I looked
at in the nibble editor. Track $01 was
"D5 AA 97", and the value at $A982 is
$97. Track $11 was "D5 AA B5", and the
value at $A992 is $B5.

Now, what's at $BB00?

                   ~

               Chapter 3
    In Which We Examine Our Options


The routine at $B6BC that copies the
RWTS patcher to $A971 exits via $BB00.
There isn't usually anything at $BB00;
it's used as scratch space during every
disk read and write call. So any code
there is immediately suspicious.

I don't have that code in memory yet. I
will need to extend my previous boot
trace program to capture it.

*9600<C600.C6FFM

; set up callback after loading sectors
; but before jumping to $B6BC
96F8-   A9 4C       LDA   #$4C
96FA-   8D 4A 08    STA   $084A
96FD-   A9 0A       LDA   #$0A
96FF-   8D 4B 08    STA   $084B
9702-   A9 97       LDA   #$97
9704-   8D 4C 08    STA   $084C

; start the boot
9707-   4C 01 08    JMP   $0801

; (callback is here)
; copy the sectors we loaded into lower
; memory so they survive a reboot to my
; work disk
970A-   A2 0A       LDX   #$0A
970C-   A0 00       LDY   #$00
970E-   B9 00 B6    LDA   $B600,Y
9711-   99 00 26    STA   $2600,Y
9714-   C8          INY
9715-   D0 F7       BNE   $970E
9717-   EE 10 97    INC   $9710
971A-   EE 13 97    INC   $9713
971D-   CA          DEX
971E-   D0 EE       BNE   $970E

; turn off slot 6 drive motor
9720-   AD E8 C0    LDA   $C0E8

; reboot to my work disk
9723-   4C 00 C5    JMP   $C500

*BSAVE TRACE1,A$9600,L$126

*9600G
...reboots slot 6...
...reboots slot 5...

]BSAVE OBJ.B600-BFFF,A$2600,L$A00
]CALL -151

*B600<2600.2EFFM

*BB00L

; move this code elsewhere
BB00-   A0 00       LDY   #$00
BB02-   B9 00 BB    LDA   $BB00,Y
BB05-   99 00 AB    STA   $AB00,Y
BB08-   B9 00 BC    LDA   $BC00,Y
BB0B-   99 00 AC    STA   $AC00,Y
BB0E-   C8          INY
BB0F-   D0 F1       BNE   $BB02

; and continue from there
BB11-   4C 26 AB    JMP   $AB26

OK then.

*BB11:60

*BB00G

*AB26L

AB26-   AD 55 AC    LDA   $AC55
AB29-   29 04       AND   #$04
AB2B-   F0 11       BEQ   $AB3E

*AC55

AC55- 84

#$84 AND #$04 is not 0, so this branch
will not be taken. So we fall through
and continue.

AB2D-   A9 02       LDA   #$02
AB2F-   A2 A5       LDX   #$A5
AB31-   20 14 AB    JSR   $AB14
AB34-   A9 AD       LDA   #$AD
AB36-   A2 08       LDX   #$08
AB38-   20 14 AB    JSR   $AB14

*AB14L

; clear memory
; A = start address (high byte)
; X = number of pages
AB14-   85 FF       STA   $FF
AB16-   A9 00       LDA   #$00
AB18-   85 FE       STA   $FE
AB1A-   A8          TAY
AB1B-   91 FE       STA   ($FE),Y
AB1D-   C8          INY
AB1E-   D0 FB       BNE   $AB1B
AB20-   E6 FF       INC   $FF
AB22-   CA          DEX
AB23-   D0 F6       BNE   $AB1B
AB25-   60          RTS

So we're clearing basically all of
main memory. Lovely.

; clear screen
AB3B-   20 58 FC    JSR   $FC58
AB3E-   A6 2B       LDX   $2B

; #$84 AND #$08 is 0, so we take this
; branch and skip over the next few
; instructions
AB40-   AD 55 AC    LDA   $AC55
AB43-   29 08       AND   #$08
AB45-   F0 08       BEQ   $AB4F

;[skipped]
;AB47-   BD 8D C0    LDA   $C08D,X
;AB4A-   BD 8E C0    LDA   $C08E,X
;AB4D-   30 2E       BMI   $AB7D

; #$84 and #$10 is 0, so we take this
; branch and skip over the next few
; instructions
AB4F-   AD 55 AC    LDA   $AC55
AB52-   29 10       AND   #$10
AB54-   F0 08       BEQ   $AB5E

;AB56-   BD 8D C0    LDA   $C08D,X
;AB59-   BD 8E C0    LDA   $C08E,X
;AB5C-   10 1F       BPL   $AB7D

I like how this copy protection routine
comes with options. Flip a bit, and it
checks to ensure the disk is write-
protected. Flip a different bit, and it
checks to ensure the disk is NOT write-
protected.

; BIT #$84 will set the sign bit to 1,
; which means we will not take this
; branch. We will fall through and
; continue at $AB63.
AB5E-   2C 55 AC    BIT   $AC55
AB61-   10 03       BPL   $AB66

; will look at this in a minute
AB63-   20 80 AB    JSR   $AB80

; assuming that subroutine returns,
; we continue to legitimate code
AB66-   4C 00 B7    JMP   $B700

So the routine at $AB80 is probably
important, and it's probably what was
causing my EDD bit copy to reboot.

                   ~

               Chapter 4
      In Which We Run In Circles


*AB80L

; tell the RWTS that we're on track 0
; (which happens to be true)
AB80-   A9 00       LDA   #$00
AB82-   8D 78 04    STA   $0478

; get the next address field
AB85-   A6 2B       LDX   $2B
AB87-   20 44 B9    JSR   $B944

; loop until we find one that says it's
; sector #$FF (WTF?)
AB8A-   A5 2D       LDA   $2D
AB8C-   C9 FF       CMP   #$FF
AB8E-   D0 F7       BNE   $AB87

AB90-   A9 01       LDA   #$01
AB92-   85 FF       STA   $FF

; patch the RWTS to look for the per-
; track custom address prologue, based
; on the same table we saw earlier
AB94-   A4 FF       LDY   $FF
AB96-   B9 81 A9    LDA   $A981,Y
AB99-   8D 6A B9    STA   $B96A

; seek to that track
AB9C-   98          TYA
AB9D-   0A          ASL
AB9E-   20 A0 B9    JSR   $B9A0

; read the next address field
ABA1-   20 44 B9    JSR   $B944

; compare the sector number to an array
; that starts at $AC30
ABA4-   A5 2D       LDA   $2D
ABA6-   A4 FF       LDY   $FF
ABA8-   D9 30 AC    CMP   $AC30,Y
ABAB-   D0 D0       BNE   $AB7D

*AB7DL

AB7D-   4C A6 FA    JMP   $FAA6

If we don't find the right sector,
reboot immediately. No second chances.

; increment track number
ABAD-   E6 FF       INC   $FF

; and loop through all tracks on disk
ABAF-   A5 FF       LDA   $FF
ABB1-   C9 23       CMP   #$23
ABB3-   90 DF       BCC   $AB94
ABB5-   A9 22       LDA   #$22
ABB7-   8D 78 04    STA   $0478
ABBA-   60          RTS

Here is the array of sector numbers it
expects to find:

*AC31.

AC31- .. 07 0E 05 0C 05 0C 03
AC38- 0A 01 0A 01 08 0F 06 0E
AC40- 05 0C 03 0A 03 0A 01 08
AC48- 0F 08 0F 06 0D 04 0D 04
AC50- 0B 02 09

Baudville disks do something similar to
this, although only for a few tracks,
and with more leeway for error. But the
basic technique is the same. The reason
this is copy protection is because it
relies on the physical layout of the
disk.

Each track is a circle -- literally, a
physical floppy disk is a series of
concentric circles of bits. Circles can
start anywhere; the "start" of a track
could be at any angular position on the
physical disk. DOS doesn't care about
the physical layout of one track
compared to the next track. When you
seek to a track and ask to read a
sector, DOS will just look at the
address fields as they spin past the
drive head until it finds the sector
you asked for.

Bit copy programs can reproduce the
bits on a track (within limits), but by
default they won't try to reproduce the
physical layout of the original
disk. So tracks will end up "starting"
at a random position on the circle.
Again, DOS doesn't care about the
physical layout of each track compared
to any other track. It's specifically
built not to care. But this copy
protection routine cares.

It resets itself by looking for a
specific marker on track 0, then it
seeks to the next track and only looks
at the very next address field that
comes under the drive head. If it's the
"wrong" sector, boom, immediate reboot.
Then it seeks to next track and checks
the number of the very next sector. And
so on, for every track, all the way up
to track $22.

Bit copy programs can (sometimes)
reproduce the physical layout of a disk
with the "synchronize tracks" option,
but it is very sensitive to the speed
of your floppy drive. Drives can vary
by up to 10% in how fast they spin. If
yours isn't calibrated at exactly the
same speed as the drive that wrote the
original disk, the physical layout of
the copy will slowly drift from the
layout of the original -- especially on
higher tracks. It is entirely possible
that you could produce a bit copy of
this disk that almost passed this
protection check, then failed on the
last track.

In any case, the protection check has
no side effects. If it passes, it just
returns gracefully to the caller, which
continues the boot at $B700. I should
be able to bypass the entire thing.

More importantly, it doesn't alter the
RWTS any further. Which means I now
have everything I need to convert this
disk to a standard format.

                   ~

               Chapter 5
   In Which We Use The Original Disk
      As A Weapon Against Itself,
        And It Goes Swimmingly


Advanced Demuffin is a cracker's tool
to convert disks to a standard format.
It takes a copy of the original disk's
RWTS (which you must supply), uses that
to read the original, while using its
own copy of a standard RWTS to write
out a copy in a standard format, sector
by sector.

I've included the latest version of
Advanced Demuffin on my work disk.

*BLOAD ADVANCED DEMUFFIN 1.5

*BLOAD OBJ.B600-BFFF,A$3600

*FE89G FE93G     ; disconnect DOS
*B600<3600.3FFFM ; move RWTS into place

*B6C7:60
*B6BCG           ; install RWTS patcher

*800G            ; launch Advanced Dem.

[press "C" to convert disk]

["Y" to change default values]

                 --v--

ADVANCED DEMUFFIN 1.5    (C) 1983, 2014
ORIGINAL BY THE STACK    UPDATES BY 4AM
=======================================


INPUT ALL VALUES IN HEX


SECTORS PER TRACK? (13/16) 16

START TRACK: $01        <-- change this
START SECTOR: $00
END TRACK: $22
END SECTOR: $0F

INCREMENT: 1

MAX # OF RETRIES: 0

COPY FROM DRIVE 1
TO DRIVE: 2
=======================================
16SC $01,$00-$22,$0F BY1.0 S6,D1->S6,D2

                 --^--

[S6,D1=original disk]
[S6,D2=blank disk]

And here we go...

                 --v--

ADVANCED DEMUFFIN 1.5    (C) 1983, 2014
ORIGINAL BY THE STACK    UPDATES BY 4AM
=======PRESS ANY KEY TO CONTINUE=======
TRK: ..................................
+.5:
    0123456789ABCDEF0123456789ABCDEF012
SC0: ..................................
SC1: ..................................
SC2: ..................................
SC3: ..................................
SC4: ..................................
SC5: ..................................
SC6: ..................................
SC7: ..................................
SC8: ..................................
SC9: ..................................
SCA: ..................................
SCB: ..................................
SCC: ..................................
SCD: ..................................
SCE: ..................................
SCF: ..................................
=======================================
16SC $01,$00-$22,$0F BY1.0 S6,D1->S6,D2

                 --^--

Track 0 is unprotected, so I used Copy
II Plus manual sector copy to copy it.
(Not shown; it's under "bit copy" if
you're looking for it. Just start and
end on track 0.)

                   ~

               Chapter 6
In Which We Move Fast And Break Things,
     But Mostly Just Break Things


Now I have a completely broken copy
that won't boot because it won't pass
its every-track-is-synchronized-with-
every-other-track protection check.

Turning to my trusty Disk Fixer sector
editor, I can bypass the protection
check by jumping directly to $B700
(instead of $BB00) after installing the
RWTS patcher.

T00,S00,$C9: BB -> B7

Now my copy can't read itself because
it's altering its RWTS on every track.
Let's fix that too. The RWTS patcher
ends up at $A971, but it's stored on
T00,S00.

T00,S00,$CF: 8D -> 2C
T00,S00,$D2: 8D -> 2C

The result:

                 --v--

T00,S00
----------- DISASSEMBLY MODE ----------
00BC:A0 33          LDY   #$33
00BE:B9 CA B6       LDA   $B6CA,Y
00C1:99 71 A9       STA   $A971,Y
00C4:88             DEY
00C5:10 F7          BPL   $00BE

; bypass protection check at $BB00
00C7:4C 00 B7       JMP   $B700
00CA:48             PHA
00CB:A8             TAY
00CC:B9 81 A9       LDA   $A981,Y

; these function as NOPs
00CF:2C 84 BC       BIT   $BC84
00D2:2C 6A B9       BIT   $B96A
00D5:A0 01          LDY   #$01
00D7:4C 5D BE       JMP   $BE5D

                 --^--

]PR#6
...boots, loads part of DOS, then
   starts grinding and crashes...

A90A-    A=86 X=00 Y=1B P=31 S=F8
*

I've missed something.

                   ~

               Chapter 7
      In Which We Muddle Through


Listening closely to my not-quite-
working copy, it appears that it's
loading part of DOS, then giving up.
Which is weird, because I would expect
an RWTS to work 100% or not at all.

A quick memory test confirms this.

]PR#6
...boots, crashes...

A90A-    A=86 X=00 Y=1B P=31 S=F8

; fill memory with an unusual byte
*800:FD N 801<800.BFFEM

*C600G
...boots, crashes...

A90A-    A=22 X=00 Y=1B P=31 S=F8

Poking through the monitor confirms
that

  - $0800..$08FF contains the boot
    sector (expected)
  - $0900..$9AFF contains $FD (i.e.
    is unchanged, which is expected)
  - $9A00..$A0FF contain the relevant
    portions of DOS (expected)
  - $A900..$B5FF contain the relevant
    portions of DOS (expected)
  - $B600..$BFFF contains the RWTS
    (expected)

In fact, only a few pages are missing:
$A100..$A8FF. In memory, they are all
still the $FD byte that I set before
rebooting. On disk, these pages are
stored on track 1, sector 0-7. Maybe
Advanced Demuffin corrupted these? No,
that doesn't make any sense. They would
end up as nonsense (or zero) -- not a
range of $FD bytes. (This is actually
why I always set memory to an unusual
byte, to distinguish in cases like this
where I don't quite know what's going
on.)

Turning to my trusty Disk Fixer sector
editor, I can confirm that all of those
sectors are intact on disk. They're
just not being loaded, which obviously
causes DOS to crash eventually. (Try
wiping out 20% of *your* operating
system and see how far *you* get.)

.
.
.
. Time passes. It is getting dark. You
. are likely to be eaten by a grue.
.
.
.

OK. DOS loads from the top down. That
is, it loads $B500 from T02,S04, then
$B400 from T02,S03, &c. Eventually it
seeks backwards to track 1, then track
0, then goes and loads the startup
program and so on.

When it reads T01,S07 into $A900, that
works. In fact, it's the last sector
that works... for a while. Then it
fails for the rest of track 1.  BUT BUT
BUT then it succeeds again once it gets
to track 0 (reading into $9A00..$A0FF).
Which makes no sense whatsoever.

Oh my God. Of course. We installed an
RWTS patcher at $A971. We neutralized
it by patching the code on T00,S00, but
then... our patched routine got
overwritten by the actual contents of
T01,S08.

                 --v--

T01,S08
----------- DISASSEMBLY MODE ----------
0071:48             PHA
0072:A8             TAY
0073:B9 81 A9       LDA   $A981,Y
0076:8D 84 BC       STA   $BC84
0079:8D 6A B9       STA   $B96A
007C:A0 01          LDY   #$01
007E:4C 5D BE       JMP   $BE5D

                 --^--

Words can not express how annoyed I am.

T01,S08,$76: 8D -> 2C
T01,S08,$79: 8D -> 2C

]PR#6
...works, and it is annoying
   I mean glorious
   but also really, really annoying...

Quod erat liberandum.

                   ~

            Acknowledgments


Many thanks to Michael Whalen, Paul
Hagstrom, Jorma Honkanen, and Peter
Ferrie for their feedback on drafts of
this write-up and the accompanying
video.

---------------------------------------
A 4am crack                    No. 1000
------------------EOF------------------
